import chokidar from 'chokidar'
import Filemage from '../utils/Filemage.js'
import common from '../utils/common.js'
import util from 'node:util'
import fs from 'fs'
import moment from 'moment'
import lodash from 'lodash'
import chalk from 'chalk'
import { segment } from '../component/icqq/index.js'
import path from 'node:path'
import timer from '../utils/Timer.js'
import Config from '../model/base/Config.js'
import { __dirname, pluginName, pluginNameEng } from '../components/Path.js'
import getBot from '../adapter/adapter.js'

class pluginLoader {
  constructor () {
    this.apps = [] // 插件功能列表
    this.priority = [] // 任务队列
    this.watcher = {}
    this.task = [] // 定时任务
    this.filemage = new Filemage(__dirname + 'apps/')
  }

  async getApps () {
    let files = this.filemage.GetfileList()
    for (let f of files) {
      try {
        if (this.filemage.isDirectory(f)) continue
        if (!f.endsWith('.js')) continue
        // 加载js插件
        let apps = await import(`../apps/${f}`)
        if (!apps) continue
        for (let a in apps) {
          let plugin = new apps[a]()
          // 启动init
          plugin.init && plugin.init()
          if (plugin.task) {
            if (Array.isArray(plugin.task)) {
              plugin.task.forEach((item, index) => {
                if (typeof plugin.task[index].fnc === 'string') { plugin.task[index].fnc = plugin[plugin.task[index].fnc] }
              })
            } else {
              if (typeof plugin.task.fnc === 'string') { plugin.task.fnc = plugin[plugin.task.fnc] }
            }
          }
          this.priority.push({
            name: plugin.name,
            event: plugin.event,
            class: apps[a],
            key: `${f}`,
            priority: plugin.priority,
            task: plugin.task
          })
        }
        /** 监听热更新 */
        this.watch('apps', f)
      } catch (err) {
        logger.error(`${pluginNameEng} ${f}加载失败`, err)
      }
    }
    this.collectTimeTask()
    this.creatTask()
    // this.dealreply()
  }

  async deal (e) {
    let priority = []
    if (this.isYunzai()) {
      // 是否只关心at
      if (!(await this.onlyReplyAt(e))) return
      if (!(await this.checkBlack(e))) return
    } else {
      this.dealEvent(e)
      this.reply(e)
    }
    if (this.isonoff(e)) return
    if (!this.filterGroup(e)) return
    if (e?.isGroup && e?.group?.mute_left) return
    this.priority.forEach((v, index) => {
      if (this.isnoapp(v.key.replace('.js', ''))) return
      let p
      if (v?.class) {
        p = new v.class(e)
      }
      p.e = e
      if (!this.filtEvent(e, p)) return
      p.bot = getBot(e)
      priority.push(p)
    })

    priority = lodash.orderBy(priority, ['priority'], ['asc'])

    // for (let plugin of priority) {
    //   /** 上下文hook */
    //   if (plugin.getContext) {
    //     let context = plugin.getContext();
    //     if (!lodash.isEmpty(context)) {
    //       for (let fnc in context) {
    //         plugin[fnc](context[fnc]);
    //       }
    //       return;
    //     }
    //   }

    //   /** 群上下文hook */
    //   if (plugin.getContextGroup) {
    //     let context = plugin.getContextGroup();
    //     if (!lodash.isEmpty(context)) {
    //       for (let fnc in context) {
    //         plugin[fnc](context[fnc]);
    //       }
    //       return;
    //     }
    //   }
    // }
    for (const i of priority) {
      /** 上下文hook */
      if (!i.getContext) continue
      const context = {
        ...i.getContext(),
        ...i.getContext(false, true)
      }
      if (!lodash.isEmpty(context)) {
        let ret
        for (const fnc in context) { ret ||= await Object.assign(new i.class(e), { e })[fnc](context[fnc]) }
        if (ret === 'continue') continue
        return
      }
    }
    for (const i of priority) {
      if (i.accept) {
        const res = await Object.assign(new i.class(e), { e }).accept(e)
        if (res === 'return') return
        if (res) break
      }
    }
    b: for (let i of priority) {
      /** 正则匹配 */
      if (i.rule) {
        c: for (let v of i.rule) {
          /** 判断事件 */
          if (v.event && !this.filtEvent(e, v)) continue b
          if (new RegExp(v.reg).test(e.msg)) {
            e.reg = v.reg
            e.logFnc = `[${i.name}][${v.fnc}]`
            if (v.log !== false) {
              logger.mark(
                `${e.logFnc}${e.logText} ${lodash.truncate(e.msg, {
                  length: 80
                })}`
              )
            }
            if (this.filtPermission(e, v)) {
              try {
                let res
                if (v?.fuc) {
                  res = v.fuc(e)
                } else {
                  res = i[v.fnc](e)
                }
                let start = Date.now()
                if (util.types.isPromise(res)) res = await res

                if (res !== false) {
                /** 设置冷却cd */
                  if (v.log !== false) {
                    logger.mark(
                      chalk.rgb(0, 255, 0)(`[${pluginName}]`),
                      chalk.rgb(255, 255, 0)(`[${i.name}]`),
                      chalk.rgb(
                        128,
                        255,
                        0
                      )(`执行了${chalk.rgb(204, 255, 204)(v.fnc)}方法`)
                    )
                    logger.mark(
                      `${e.logFnc} ${lodash.truncate(e.msg, {
                        length: 80
                      })} 处理完成 ${Date.now() - start}ms`
                    )
                  }
                  break b
                }
              } catch (error) {
                logger.error(`${e.logFnc}`)
                logger.error(error.stack)
                break c
              }
            }
          }
        }
      }
    }
  }

  async getConfig () {
    if (this.isYunzai()) {
      let { default: config } = await import('../../../lib/config/config.js')
      return config
    }
  }

  filterGroup (e) {
    let filterGroup = Config.GetCfg('system/apps').filterGroup || []
    // if (e.msg && e.msg.includes("#千羽群功能")) {
    //     return true
    // }
    if (filterGroup.includes(e.group_id)) {
      return false
    }
    return true
  }

  async checkBlack (e) {
    let other = (await this.getConfig()).getOther()

    if (e.test) return true

    /** 黑名单用户 */
    if (
      other.blackUser?.length &&
      other.blackUser.includes(Number(e.user_id) || String(e.user_id))
    ) { return false }
    /** 白名单用户 */
    if (
      other.whiteUser?.length &&
      !other.whiteUser.includes(Number(e.user_id) || String(e.user_id))
    ) { return false }

    if (e.group_id) {
      /** 黑名单群 */
      if (
        other.blackGroup?.length &&
        other.blackGroup.includes(Number(e.group_id) || String(e.group_id))
      ) { return false }
      /** 白名单群 */
      if (
        other.whiteGroup?.length &&
        !other.whiteGroup.includes(Number(e.group_id) || String(e.group_id))
      ) { return false }
    }

    return true
  }

  /** 判断权限 */
  filtPermission (e, v) {
    if (!v.permission || e.isMaster) return true

    if (v.permission === 'master') {
      e.reply('暂无权限，只有主人才能操作')
      return false
    }

    if (e.isGroup) {
      if (v.permission === 'owner' && !e.member.is_owner) {
        e.reply('暂无权限，只有群主才能操作')
        return false
      }
      if (
        v.permission === 'admin' &&
        !e.member.is_owner &&
        !e.member.is_admin
      ) {
        e.reply('暂无权限，只有管理员才能操作')
        return false
      }
    }

    return true
  }

  creatTask () {
    if (process.argv[1].includes('test')) return
    this.task.forEach((val) => {
      val.job = timer.SetTimeTask(val.name, val.cron, async () => {
        try {
          if (typeof val.fnc === 'string') {
            val.fnc = val.that[val.fnc]
          }
          if (!val.fnc || val.fnc === undefined) return
          if (val.log === true) {
            logger.mark(`开始定时任务：${val.name}`)
          }
          val.that.bot = getBot()
          let res = val.fnc.call(val.that, val.name)
          if (util.types.isPromise(res)) res = await res
          if (val.log === true) {
            logger.mark(`定时任务完成：${val.name}`)
          }
        } catch (error) {
          logger.error(`定时任务报错：${val.name}`)
          logger.error(error)
        }
      })
    })
  }

  isonoff (e) {
    if (e.msg == '#千羽开机' || e.msg == '#千羽关机') return false
    return Config.GetCfg('system/apps').isonoff
  }

  isnoapp (name) {
    let noapp = Config.GetCfg('system/apps').noapp || []
    return noapp.includes(name)
  }

  isYunzai () {
    let packjson = JSON.parse(fs.readFileSync('package.json', 'utf-8'))
    if (packjson.name !== 'qianshi-Bot') return true
    return false
  }

  // async dealreply() {
  //     if (this.isYunzai()) {
  //         let { default: loader } = await import('../../../lib/plugins/loader.js')
  //         loader.reply = this.reply
  //     }
  // }

  async onlyReplyAt (e) {
    if (!e.message || e.isPrivate) return true

    let config = await this.getConfig()

    let groupCfg = config.getGroup(e.group_id)

    if (groupCfg.onlyReplyAt != 1 || !groupCfg.botAlias) return true

    /** at机器人 */
    if (e.atBot) return true

    /** 消息带前缀 */
    if (e.hasAlias) return true

    return false
  }

  dealEvent (e) {
    let cfg = this.getConfig()
    if (e.message) {
      for (const i of e.message) {
        switch (i.type) {
          case 'text':
            e.msg = (e.msg || '') + this.dealText(i.text)
            break
          case 'image':
            if (Array.isArray(e.img)) e.img.push(i.url)
            else e.img = [i.url]
            break
          case 'at':
            if (i.qq == e.self_id) e.atBot = true
            else e.at = i.qq
            break
          case 'reply':
            e.reply_id = i.id
            if (e.group?.getMsg) e.getReply = () => e.group.getMsg(e.reply_id)
            else if (e.friend?.getMsg) { e.getReply = () => e.friend.getMsg(e.reply_id) }
            break
          case 'file':
            e.file = i
            break
          case 'xml':
          case 'json':
            e.msg =
              (e.msg || '') +
              (typeof i.data === 'string' ? i.data : JSON.stringify(i.data))
            break
        }
      }
    }

    e.logText = ''
    e.sender && (e.sender.card ||= e.sender.nickname)
    if (e.message_type === 'private' || e.notice_type === 'friend') {
      e.isPrivate = true
      e.logText = `[${e.sender?.nickname ? `${e.sender.nickname}(${e.user_id})` : e.user_id}]`

      if (!e.recall && e.message_id && e.friend?.recallMsg) { e.recall = e.friend.recallMsg.bind(e.friend, e.message_id) }
    } else if (e.message_type === 'group' || e.notice_type === 'group') {
      e.isGroup = true
      e.logText = `[${e.group_name ? `${e.group_name}(${e.group_id})` : e.group_id}, ${e.sender?.card ? `${e.sender.card}(${e.user_id})` : e.user_id}]`

      if (!e.recall && e.message_id && e.group?.recallMsg) { e.recall = e.group.recallMsg.bind(e.group, e.message_id) }
    }

    e.logText = `${logger.cyan(e.logText)}${logger.red(`[${lodash.truncate(e.msg || e.raw_message || Bot.String(e), { length: 100 })}]`)}`

    if (e.user_id && cfg.master[e.self_id]?.includes(String(e.user_id))) { e.isMaster = true }

    /** 只关注主动at msg处理 */
    if (e.msg && e.isGroup && !e.atBot) {
      const alias = cfg.getGroup(e.self_id, e.group_id).botAlias
      for (const i of Array.isArray(alias) ? alias : [alias]) {
        if (e.msg.startsWith(i)) {
          e.msg = e.msg.replace(i, '')
          e.hasAlias = true
          break
        }
      }
    }
  }

  async dealMsg (e) {
    if (e.msg) return
    if (e.message) {
      for (let val of e.message) {
        switch (val.type) {
          case 'text':
            /** 中文#转为英文 */
            val.text = val.text.replace(/＃|井/g, '#').trim()
            if (e.msg) {
              e.msg += val.text
            } else {
              e.msg = val.text.trim()
            }
            break
          case 'image':
            if (!e.img) {
              e.img = []
            }
            e.img.push(val.url)
            break
          case 'at':
            if (val.qq == Bot.uin) {
              e.atBot = true
            } else {
              /** 多个at 以最后的为准 */
              e.at = val.qq
            }
            break
          case 'file':
            e.file = { name: val.name, fid: val.fid }
            break
        }
      }
    }

    e.logText = ''

    if (e.message_type == 'private' || e.notice_type == 'friend') {
      e.isPrivate = true

      if (e.sender) {
        e.sender.card = e.sender.nickname
      } else {
        e.sender = {
          card: e.friend?.nickname,
          nickname: e.friend?.nickname
        }
      }

      e.logText = `[私聊][${e.sender.nickname}(${e.user_id})]`
    }

    if (e.message_type == 'group' || e.notice_type == 'group') {
      e.isGroup = true
      if (e.sender) {
        e.sender.card = e.sender.card || e.sender.nickname
      } else if (e.member) {
        e.sender = {
          card: e.member.card || e.member.nickname
        }
      } else if (e.nickname) {
        e.sender = {
          card: e.nickname,
          nickname: e.nickname
        }
      } else {
        e.sender = {
          card: '',
          nickname: ''
        }
      }

      if (!e.group_name) e.group_name = e.group?.name

      e.logText = `[${e.group_name}(${e.sender.card})]`
    } else if (e.detail_type === 'guild') {
      e.isGuild = true
    }
    let config = await this.getConfig()
    if (config) {
      if (e.user_id && config.masterQQ.includes(Number(e.user_id))) {
        e.isMaster = true
      }

      /** 只关注主动at msg处理 */
      if (e.msg && e.isGroup) {
        let groupCfg = config.getGroup(e.group_id)
        let alias = groupCfg.botAlias
        if (!Array.isArray(alias)) {
          alias = [alias]
        }
        for (let name of alias) {
          if (e.msg.startsWith(name)) {
            e.msg = lodash.trimStart(e.msg, name).trim()
            e.hasAlias = true
            break
          }
        }
      }
    }
  }

  collectTimeTask () {
    this.priority.forEach((priority) => {
      if (!priority?.task) return
      if (Array.isArray(priority.task)) {
        priority.task.forEach((val) => {
          if (!val.cron) return
          if (!val.name) throw new Error('插件任务名称错误')
          val.that = new priority.class()
          this.task.push(val)
        })
      } else {
        if (priority.task.fnc && priority.task.cron) {
          if (!priority.task.name) throw new Error('插件任务名称错误')
          let task = priority.task
          task.that = new priority.class()
          this.task.push(task)
        }
      }
    })
  }

  filtEvent (e, v) {
    let event = v.event.split('.')
    let eventMap = {
      message: ['post_type', 'message_type', 'sub_type'],
      notice: ['post_type', 'notice_type', 'sub_type'],
      request: ['post_type', 'request_type', 'sub_type']
    }
    let newEvent = []
    event.forEach((val, index) => {
      if (val === '*') {
        newEvent.push(val)
      } else if (eventMap[e.post_type]) {
        newEvent.push(e[eventMap[e.post_type][index]])
      }
    })
    newEvent = newEvent.join('.')

    if (v.event == newEvent) return true

    return false
  }

  /** 监听热更新 */
  watch (dirName, appName) {
    this.watchDir(dirName)
    if (this.watcher[`${appName}`]) return

    let file = `./plugins/${pluginNameEng}/apps/${appName}`
    const watcher = chokidar.watch(file)
    let key = `${appName}`

    /** 监听修改 */
    watcher.on('change', async (path) => {
      if (this.task.length) timer.CancelAllTimeTask()
      logger.mark(`[修改${pluginName}][${appName}]`)

      let tmp = {}
      try {
        tmp = await import(`../${dirName}/${appName}?${moment().format('x')}`)
      } catch (error) {
        logger.error(`载入插件错误：${logger.red(appName)}`)
        logger.error(decodeURI(error.stack))
        return
      }

      if (tmp.apps) tmp = { ...tmp.apps }
      lodash.forEach(tmp, (p) => {
        /* eslint-disable new-cap */
        let plugin = new p()
        if (plugin.task) {
          if (Array.isArray(plugin.task)) {
            plugin.task.forEach((item, index) => {
              if (typeof plugin.task[index].fnc === 'string') { plugin.task[index].fnc = plugin[plugin.task[index].fnc] }
            })
          } else {
            if (typeof plugin.task.fnc === 'string') { plugin.task.fnc = plugin[plugin.task.fnc] }
          }
        }
        for (let i in this.priority) {
          if (this.priority[i].key == key) {
            this.priority[i].class = p
            this.priority[i].priority = plugin.priority
            this.priority[i].task = plugin.task
          }
        }
      })

      this.priority = lodash.orderBy(this.priority, ['priority'], ['asc'])

      this.collectTimeTask()
      this.creatTask()
    })

    /** 监听删除 */
    watcher.on('unlink', async (path) => {
      logger.mark(`[卸载插件][${appName}]`)
      for (let i in this.priority) {
        if (this.priority[i].key == key) {
          this.priority.splice(i, 1)
          /** 停止更新监听 */
          this.watcher[`${appName}`].removeAllListeners('change')
          break
        }
      }
    })

    this.watcher[`${appName}`] = watcher
  }

  /** 监听文件夹更新 */
  watchDir (dirName) {
    if (this.watcher[dirName]) return

    let file = `./plugins/${pluginNameEng}/${dirName}/`
    const watcher = chokidar.watch(file)

    /** 热更新 */
    setTimeout(() => {
      /** 新增文件 */
      watcher.on('add', async (PluPath) => {
        let appName = path.basename(PluPath)
        if (!appName.endsWith('.js')) return
        if (!this.filemage.ExistsFile(`${appName}`)) return

        let key = `${appName}`

        this.watch(dirName, appName)

        /** 太快了延迟下 */
        await new common().sleep(500)

        logger.mark(`[新增插件][${appName}]`)
        let tmp = {}
        try {
          tmp = await import(
            `../${dirName}/${appName}?${moment().format('X')}`
          )
        } catch (error) {
          logger.error(`载入插件错误：${logger.red(appName)}`)
          logger.error(decodeURI(error.stack))
          return
        }

        if (tmp.apps) tmp = { ...tmp.apps }

        lodash.forEach(tmp, (p) => {
          if (!p.prototype) {
            logger.error(`[载入失败][${appName}] 格式错误已跳过`)
            return
          }
          /* eslint-disable new-cap */
          let plugin = new p()
          if (plugin.task) {
            plugin.task.fnc = plugin[plugin.task.fnc]
          }
          plugin.init && plugin.init()
          this.priority.push({
            name: plugin.name,
            event: plugin.event,
            class: p,
            key: `${appName}`,
            priority: plugin.priority,
            task: plugin.task
          })
        })

        /** 优先级排序 */
        this.priority = lodash.orderBy(this.priority, ['priority'], ['asc'])
      })
    }, 500)

    this.watcher[dirName] = watcher
  }
}

export default new pluginLoader()
